/*
 * Driver for CL-GD546x -- The Laguna family
 *
 * lg_driver.c
 *
 * (c) 1998 Corin Anderson.
 *          corina@the4cs.com
 *          Tukwila, WA
 *
 * This driver is derived from the cir_driver.c module.
 * Original authors and contributors list include:
 *	Radoslaw Kapitan, Andrew Vanderstock, Dirk Hohndel,
 *	David Dawes, Andrew E. Mileski, Leonard N. Zubkoff,
 *	Guy DESBIEF, Itai Nahshon.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#define EXPERIMENTAL

/* All drivers should typically include these */
#include "xf86.h"
#include "xf86_OSproc.h"

/* All drivers need this */

#include "compiler.h"

/* Drivers that need to access the PCI config space directly need this */
#include "xf86Pci.h"

/* All drivers using the vgahw module need this */
/* This driver needs to be modified to not use vgaHW for multihead operation */
#include "vgaHW.h"

#if GET_ABI_MAJOR(ABI_VIDEODRV_VERSION) < 6
#include "xf86RAC.h"
#include "xf86Resources.h"
#endif

/* All drivers initialising the SW cursor need this */
#include "mipointer.h"

/* need this for inputInfo */
#include "inputstr.h"

#include "micmap.h"

/* Needed by the Shadow Framebuffer */
#include "shadowfb.h"

#include "xf86int10.h"

#include "fb.h"

#include "xf86DDC.h"

#undef LG_DEBUG

#include "cir.h"
#define _LG_PRIVATE_
#include "lg.h"

#include "xf86xv.h"
#include <X11/extensions/Xv.h>

/*
 * Forward definitions for the functions that make up the driver.
 */

/* Mandatory functions */
Bool LgPreInit(ScrnInfoPtr pScrn, int flags);
Bool LgScreenInit(SCREEN_INIT_ARGS_DECL);
Bool LgEnterVT(VT_FUNC_ARGS_DECL);
void LgLeaveVT(VT_FUNC_ARGS_DECL);
static Bool	LgCloseScreen(CLOSE_SCREEN_ARGS_DECL);
static Bool	LgSaveScreen(ScreenPtr pScreen, Bool mode);

/* Required if the driver supports mode switching */
Bool LgSwitchMode(SWITCH_MODE_ARGS_DECL);
/* Required if the driver supports moving the viewport */
void LgAdjustFrame(ADJUST_FRAME_ARGS_DECL);

/* Optional functions */
void LgFreeScreen(FREE_SCREEN_ARGS_DECL);
ModeStatus LgValidMode(SCRN_ARG_TYPE arg, DisplayModePtr mode,
		       Bool verbose, int flags);

/* Internally used functions */
static void LgRestoreLgRegs(ScrnInfoPtr pScrn, LgRegPtr lgReg);
static int LgFindLineData(int displayWidth, int bpp);
static CARD16 LgSetClock(CirPtr pCir, vgaHWPtr hwp, int freq);
static void lg_vgaHWSetMmioFunc(vgaHWPtr hwp, CARD8 *base);

static void LgDisplayPowerManagementSet(ScrnInfoPtr pScrn,
				        int PowerManagementMode, int flags);

/*
 * This is intentionally screen-independent.  It indicates the binding
 * choice made in the first PreInit.
 */
static int pix24bpp = 0;

/*
 * This contains the functions needed by the server after loading the
 * driver module.  It must be supplied, and gets added the driver list by
 * the Module Setup funtion in the dynamic case.  In the static case a
 * reference to this is compiled in, and this requires that the name of
 * this DriverRec be an upper-case version of the driver name.
 */

typedef enum {
	OPTION_HW_CURSOR,
	OPTION_PCI_RETRY,
	OPTION_ROTATE,
	OPTION_SHADOW_FB,
	OPTION_NOACCEL
} LgOpts;

static const OptionInfoRec LgOptions[] = {
    { OPTION_HW_CURSOR,		"HWcursor",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_NOACCEL,		"NoAccel",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_SHADOW_FB,         "ShadowFB",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_ROTATE, 	        "Rotate",	OPTV_ANYSTR,	{0}, FALSE },
    /* fifo_conservative/aggressive; fast/med/slow_dram; ... */
    { -1,					NULL,		OPTV_NONE,		{0}, FALSE }
};


/*                                1/4bpp   8bpp   15/16bpp  24bpp  32bpp */
static int gd5462_MaxClocks[] = { 170000, 170000, 135100, 135100,  85500 };
static int gd5464_MaxClocks[] = { 170000, 250000, 170000, 170000, 135100 };
static int gd5465_MaxClocks[] = { 170000, 250000, 170000, 170000, 135100 };

LgLineDataRec LgLineData[] = {
	{ 5,  640, 0},		/* We're rather use skinny tiles, so put all of */
	{ 8, 1024, 0},		/* them at the head of the table */
	{10, 1280, 0},
	{13, 1664, 0},
	{16, 2048, 0},
	{20, 2560, 0},
	{10, 2560, 1},
	{26, 3328, 0},
	{ 5, 1280, 1},
	{ 8, 2048, 1},
	{13, 3328, 1},
	{16, 4096, 1},
	{20, 5120, 1},
	{26, 6656, 1},
	{-1, -1, -1}		/* Sentinal to indicate end of table */
};

static int LgLinePitches[4][11] = {
	/*  8 */ { 640, 1024, 1280, 1664, 2048, 2560, 3328, 4096, 5120, 6656, 0 },
	/* 16 */ { 320,  512,  640,  832, 1024, 1280, 1664, 2048, 2560, 3328, 0 },
	/* 24 */ { 213,  341,  426,  554,  682,  853, 1109, 1365, 1706, 2218, 0 },
	/* 32 */ { 160,  256,  320,  416,  512,  640,  832, 1024, 1280, 1664, 0 }
};

#ifdef XFree86LOADER

#define LG_MAJOR_VERSION 1
#define LG_MINOR_VERSION 0
#define LG_PATCHLEVEL 0

static MODULESETUPPROTO(lgSetup);

static XF86ModuleVersionInfo lgVersRec =
{
	"cirrus_laguna",
	MODULEVENDORSTRING,
	MODINFOSTRING1,
	MODINFOSTRING2,
	XORG_VERSION_CURRENT,
	LG_MAJOR_VERSION, LG_MINOR_VERSION, LG_PATCHLEVEL,
	ABI_CLASS_VIDEODRV,			/* This is a video driver */
	ABI_VIDEODRV_VERSION,
	MOD_CLASS_NONE,
	{0,0,0,0}
};

/*
 * This is the module init data.
 * Its name has to be the driver name followed by ModuleData.
 */
_X_EXPORT XF86ModuleData cirrus_lagunaModuleData = {
    &lgVersRec,
    lgSetup,
    NULL
};

static pointer
lgSetup(pointer module, pointer opts, int *errmaj, int *errmin)
{
    static Bool setupDone = FALSE;
    
    if (!setupDone) {
	setupDone = TRUE;
    }
    return (pointer)1;
}

#endif /* XFree86LOADER */

_X_EXPORT const OptionInfoRec *
LgAvailableOptions(int chipid)
{
    return LgOptions;
}

_X_EXPORT ScrnInfoPtr
LgProbe(int entity)
{
    ScrnInfoPtr pScrn = NULL;
    if ((pScrn = xf86ConfigPciEntity(pScrn, 0, entity, CIRPciChipsets,
					   NULL, NULL, NULL, NULL, NULL))) {
	pScrn->PreInit		= LgPreInit;
	pScrn->ScreenInit	= LgScreenInit;
	pScrn->SwitchMode	= LgSwitchMode;
	pScrn->AdjustFrame	= LgAdjustFrame;
	pScrn->EnterVT		= LgEnterVT;
	pScrn->LeaveVT		= LgLeaveVT;
	pScrn->FreeScreen	= LgFreeScreen;
	pScrn->ValidMode	= LgValidMode;
    }
    return pScrn;
}


static Bool
LgGetRec(ScrnInfoPtr pScrn)
{
	CirPtr pCir;

	if (pScrn->driverPrivate != NULL)
		return TRUE;

	pScrn->driverPrivate = xnfcalloc(sizeof(CirRec), 1);
	((CirPtr)pScrn->driverPrivate)->chip.lg = xnfcalloc(sizeof(LgRec),1);

	/* Initialize it */
	pCir = CIRPTR(pScrn);
	pCir->chip.lg->oldBitmask = 0x00000000;

	return TRUE;
}

static void
LgFreeRec(ScrnInfoPtr pScrn)
{
	if (pScrn->driverPrivate == NULL)
		return;
	free(pScrn->driverPrivate);
	pScrn->driverPrivate = NULL;
}



/*
 * LgCountRAM --
 *
 * Counts amount of installed RAM
 */

/* XXX We need to get rid of this PIO (MArk) */
static int
LgCountRam(ScrnInfoPtr pScrn)
{
	vgaHWPtr hwp = VGAHWPTR(pScrn);
	CARD8 SR14;

	vgaHWProtect(pScrn, TRUE);

	/* The ROM BIOS scratchpad registers contain,
	   among other things, the amount of installed
	   RDRAM on the laguna chip. */
	SR14 = hwp->readSeq(hwp, 0x14);

	ErrorF("Scratch Pads: 0:%02x 1:%02x 2:%02x 3:%02x\n",
		hwp->readSeq(hwp, 9), hwp->readSeq(hwp, 10),
		SR14, hwp->readSeq(hwp, 0x15));

	vgaHWProtect(pScrn, FALSE);

	return 1024 * ((SR14&0x7) + 1);

	/* !!! This function seems to be incorrect... */
}

static xf86MonPtr
LgDoDDC(ScrnInfoPtr pScrn)
{
	CirPtr pCir = CIRPTR(pScrn);
	xf86MonPtr MonInfo = NULL;

	/* Map the CIR memory and MMIO areas */
	if (!CirMapMem(pCir, pScrn->scrnIndex))
		return FALSE;

#if LGuseI2C
	if (!LgI2CInit(pScrn)) {
		xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "I2C initialization failed\n");

		goto unmap_out;
	}

	/* Read and output monitor info using DDC2 over I2C bus */
	MonInfo = xf86DoEDID_DDC2(pScrn->scrnIndex, pCir->I2CPtr1);
	xf86DrvMsg(pScrn->scrnIndex, X_INFO, "I2C Monitor info: %p\n",
		   (void *)MonInfo);
	xf86PrintEDID(MonInfo);
	xf86DrvMsg(pScrn->scrnIndex, X_INFO, "end of I2C Monitor info\n\n");
#endif /* LGuseI2C */

	xf86SetDDCproperties(pScrn, MonInfo);

unmap_out:
	CirUnmapMem(pCir, pScrn->scrnIndex);

	return MonInfo;
}

/* Mandatory */
Bool
LgPreInit(ScrnInfoPtr pScrn, int flags)
{
	CirPtr pCir;
	vgaHWPtr hwp;
	MessageType from;
	int i;
	ClockRangePtr clockRanges;
	int fbPCIReg, ioPCIReg;
	char *s;

	if (flags & PROBE_DETECT)  {
	  cirProbeDDC( pScrn, xf86GetEntityInfo(pScrn->entityList[0])->index );
	  return TRUE;
	}
	
#ifdef LG_DEBUG
	ErrorF("LgPreInit\n");
#endif

	/* Check the number of entities, and fail if it isn't one. */
	if (pScrn->numEntities != 1)
		return FALSE;

	/* The vgahw module should be loaded here when needed */
	if (!xf86LoadSubModule(pScrn, "vgahw"))
		return FALSE;

	/*
	 * Allocate a vgaHWRec
	 */
	if (!vgaHWGetHWRec(pScrn))
		return FALSE;

	hwp = VGAHWPTR(pScrn);
	vgaHWSetStdFuncs(hwp);
	vgaHWGetIOBase(hwp);

	/* Allocate the LgRec driverPrivate */
	if (!LgGetRec(pScrn))
		return FALSE;

	pCir = CIRPTR(pScrn);
	pCir->pScrn = pScrn;

#if GET_ABI_MAJOR(ABI_VIDEODRV_VERSION) < 12
	pCir->PIOReg = hwp->PIOOffset + 0x3CE;
#else
	pCir->PIOReg = 0x3CE;
#endif

	/* Get the entity, and make sure it is PCI. */
	pCir->pEnt = xf86GetEntityInfo(pScrn->entityList[0]);
	if (pCir->pEnt->location.type != BUS_PCI)
		return FALSE;
	pCir->Chipset = pCir->pEnt->chipset;

	/* Find the PCI info for this screen */
	pCir->PciInfo = xf86GetPciInfoForEntity(pCir->pEnt->index);
#ifndef XSERVER_LIBPCIACCESS
	pCir->PciTag = pciTag(PCI_DEV_BUS(pCir->PciInfo),
			      PCI_DEV_DEV(pCir->PciInfo),
			      PCI_DEV_FUNC(pCir->PciInfo));
#endif

	if (xf86LoadSubModule(pScrn, "int10")) {
	    xf86Int10InfoPtr int10InfoPtr;
	    
	    int10InfoPtr = xf86InitInt10(pCir->pEnt->index);

	    if (int10InfoPtr)
		xf86FreeInt10(int10InfoPtr);
	}

	/* Set pScrn->monitor */
	pScrn->monitor = pScrn->confScreen->monitor;

	/*
	 * The first thing we should figure out is the depth, bpp, etc.
	 * We support both 24bpp and 32bpp layouts, so indicate that.
	 */
	if (!xf86SetDepthBpp(pScrn, 0, 0, 0, Support24bppFb | Support32bppFb |
							SupportConvert32to24 | PreferConvert32to24)) {
		return FALSE;
    }
	/* Check that the returned depth is one we support */
	switch (pScrn->depth) {
	case 8:
	case 15:
	case 16:
	case 24:
	case 32:
		/* OK */
		break;
	default:
		xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
			"Given depth (%d) is not supported by this driver\n", pScrn->depth);
		return FALSE;
	}
	xf86PrintDepthBpp(pScrn);

	/* Get the depth24 pixmap format */
	if (pScrn->depth == 24 && pix24bpp == 0)
		pix24bpp = xf86GetBppFromDepth(pScrn, 24);

	/*
	 * This must happen after pScrn->display has been set because
	 * xf86SetWeight references it.
	 */
	if (pScrn->depth > 8) {
		/* The defaults are OK for us */
		rgb zeros = {0, 0, 0};

		/* !!! I think we can force 5-6-5 weight for 16bpp here for
		   the 5462. */

		if (!xf86SetWeight(pScrn, zeros, zeros)) {
			return FALSE;
		} else {
			/* XXX check that weight returned is supported */
			;
		}
	}

	if (!xf86SetDefaultVisual(pScrn, -1))
		return FALSE;


	/* Collect all of the relevant option flags (fill in pScrn->options) */
	xf86CollectOptions(pScrn, NULL);

	/* Process the options */
	if (!(pCir->Options = malloc(sizeof(LgOptions))))
		return FALSE;
	memcpy(pCir->Options, LgOptions, sizeof(LgOptions));
	xf86ProcessOptions(pScrn->scrnIndex, pScrn->options, pCir->Options);

	pScrn->rgbBits = 6; 
	from = X_DEFAULT;
	pCir->HWCursor = FALSE;
	if (xf86GetOptValBool(pCir->Options, OPTION_HW_CURSOR, &pCir->HWCursor))
		from = X_CONFIG;

	xf86DrvMsg(pScrn->scrnIndex, from, "Using %s cursor\n",
		pCir->HWCursor ? "HW" : "SW");
	if (xf86ReturnOptValBool(pCir->Options, OPTION_NOACCEL, FALSE)) {
		pCir->NoAccel = TRUE;
		xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Acceleration disabled\n");
	}
	if (pScrn->bitsPerPixel < 8) {
		xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
			"Cannot use in less than 8 bpp\n");
		return FALSE;
	}
	/*
	 * Set the ChipRev, allowing config file entries to
	 * override.
	 */
	if (pCir->pEnt->device->chipRev >= 0) {
		pCir->ChipRev = pCir->pEnt->device->chipRev;
		xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "ChipRev override: %d\n",
			pCir->ChipRev);
	} else {
	        pCir->ChipRev = PCI_DEV_REVISION(pCir->PciInfo);
	}

	/* Cirrus swapped the FB and IO registers in the 5465 (by design). */
	if (PCI_CHIP_GD5465 == pCir->Chipset) {
		fbPCIReg = 0;
		ioPCIReg = 1;
	} else {
		fbPCIReg = 1;
		ioPCIReg = 0;
	}

	/* Find the frame buffer base address */
	if (pCir->pEnt->device->MemBase != 0) {
		/* Require that the config file value matches one of the PCI values. */
		if (!xf86CheckPciMemBase(pCir->PciInfo, pCir->pEnt->device->MemBase)) {
			xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
				"MemBase 0x%08lX doesn't match any PCI base register.\n",
				pCir->pEnt->device->MemBase);
			return FALSE;
		}
		pCir->FbAddress = pCir->pEnt->device->MemBase;
		from = X_CONFIG;
	} else {
		if (PCI_REGION_BASE(pCir->PciInfo, fbPCIReg, REGION_MEM) != 0) {
			pCir->FbAddress = PCI_REGION_BASE(pCir->PciInfo, fbPCIReg, REGION_MEM) & 0xff000000;
			from = X_PROBED;
		} else {
			xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
				"No valid FB address in PCI config space\n");
			LgFreeRec(pScrn);
			return FALSE;
		}
	}
	xf86DrvMsg(pScrn->scrnIndex, from, "Linear framebuffer at 0x%lX\n",
		(unsigned long)pCir->FbAddress);

	/* Find the MMIO base address */
	if (pCir->pEnt->device->IOBase != 0) {
		/* Require that the config file value matches one of the PCI values. */
		if (!xf86CheckPciMemBase(pCir->PciInfo, pCir->pEnt->device->IOBase)) {
			xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
				"IOBase 0x%08lX doesn't match any PCI base register.\n",
				pCir->pEnt->device->IOBase);
			return FALSE;
		}
		pCir->IOAddress = pCir->pEnt->device->IOBase;
		from = X_CONFIG;
	} else {
		if (PCI_REGION_BASE(pCir->PciInfo, ioPCIReg, REGION_MEM) != 0) {
			pCir->IOAddress = PCI_REGION_BASE(pCir->PciInfo, ioPCIReg, REGION_MEM) & 0xfffff000;
			from = X_PROBED;
		} else {
			xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
			"No valid MMIO address in PCI config space\n");
		}
	}
	xf86DrvMsg(pScrn->scrnIndex, from, "MMIO registers at 0x%lX\n",
		(unsigned long)pCir->IOAddress);

	/*
	 * If the user has specified the amount of memory in the XF86Config
	 * file, we respect that setting.
	 */
	if (pCir->pEnt->device->videoRam != 0) {
		pScrn->videoRam = pCir->pEnt->device->videoRam;
		from = X_CONFIG;
	} else {
		pScrn->videoRam = LgCountRam(pScrn);
		from = X_PROBED;
	}
	if (2048 == pScrn->videoRam) {
		/* Two-way interleaving */
		pCir->chip.lg->memInterleave = 0x40;
	} else if (4096 == pScrn->videoRam || 8192 == pScrn->videoRam) {
		/* Four-way interleaving */
		pCir->chip.lg->memInterleave = 0x80;
	} else {
		/* One-way interleaving */
		pCir->chip.lg->memInterleave = 0x00;
	}

	xf86DrvMsg(pScrn->scrnIndex, from, "VideoRAM: %d kByte\n",
				pScrn->videoRam);

	pCir->FbMapSize = pScrn->videoRam * 1024;
	pCir->IoMapSize = 0x4000;	/* 16K for moment,  will increase */

#ifndef XSERVER_LIBPCIACCESS
	pScrn->racIoFlags =   RAC_COLORMAP 
#ifndef EXPERIMENTAL
	  | RAC_VIEWPORT
#endif
;
 	xf86SetOperatingState(resVgaMem, pCir->pEnt->index, ResUnusedOpr);
	
	/* Register the PCI-assigned resources. */
	if (xf86RegisterResources(pCir->pEnt->index, NULL, ResExclusive)) {
		xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
			"xf86RegisterResources() found resource conflicts\n");
		return FALSE;
	}
#endif
	if (!xf86LoadSubModule(pScrn, "ddc")) {
		LgFreeRec(pScrn);
		return FALSE;
	}

#if LGuseI2C
	if (!xf86LoadSubModule(pScrn, "i2c")) {
		LgFreeRec(pScrn);
		return FALSE;
	}
#endif

	/* Read and print the monitor DDC information */
	pScrn->monitor->DDC = LgDoDDC(pScrn);

	/* The gamma fields must be initialised when using the new cmap code */
	if (pScrn->depth > 1) {
		Gamma zeros = {0.0, 0.0, 0.0};

		if (!xf86SetGamma(pScrn, zeros))
			return FALSE;
	}
	if (xf86GetOptValBool(pCir->Options,
			      OPTION_SHADOW_FB,&pCir->shadowFB))
	    xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "ShadowFB %s.\n",
		       pCir->shadowFB ? "enabled" : "disabled");
	    
	if ((s = xf86GetOptValString(pCir->Options, OPTION_ROTATE))) {
	    if(!xf86NameCmp(s, "CW")) {
		/* accel is disabled below for shadowFB */
		pCir->shadowFB = TRUE;
		pCir->rotate = 1;
		xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, 
			   "Rotating screen clockwise - acceleration disabled\n");
	    } else if(!xf86NameCmp(s, "CCW")) {
		pCir->shadowFB = TRUE;
		pCir->rotate = -1;
		xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,  "Rotating screen"
			   "counter clockwise - acceleration disabled\n");
	    } else {
		xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "\"%s\" is not a valid"
			   "value for Option \"Rotate\"\n", s);
		xf86DrvMsg(pScrn->scrnIndex, X_INFO, 
			   "Valid options are \"CW\" or \"CCW\"\n");
	    }
	}

	if (pCir->shadowFB && !pCir->NoAccel) {
	    xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
		       "HW acceleration not supported with \"shadowFB\".\n");
	    pCir->NoAccel = TRUE;
	}
	
	if (pCir->rotate && pCir->HWCursor) {
	    xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
		       "HW cursor not supported with \"rotate\".\n");
	    pCir->HWCursor = FALSE;
	}
	
	/* We use a programmable clock */
	pScrn->progClock = TRUE;

	/* XXX Set HW cursor use */

	/* Set the min pixel clock */
	pCir->MinClock = 12000;	/* XXX Guess, need to check this */
	xf86DrvMsg(pScrn->scrnIndex, X_DEFAULT, "Min pixel clock is %d MHz\n",
				pCir->MinClock / 1000);
	/*
	 * If the user has specified ramdac speed in the XF86Config
	 * file, we respect that setting.
	 */
	if (pCir->pEnt->device->dacSpeeds[0]) {
		ErrorF("Do not specify a Clocks line for Cirrus chips\n");
		return FALSE;
	} else {
		int speed;
		int *p;
		switch (pCir->Chipset) {
		case PCI_CHIP_GD5462:
			p = gd5462_MaxClocks;
			break;
		case PCI_CHIP_GD5464:
		case PCI_CHIP_GD5464BD:
			p = gd5464_MaxClocks;
			break;
		case PCI_CHIP_GD5465:
			p = gd5465_MaxClocks;
			break;
		default:
			ErrorF("???\n");
			return FALSE;
		}
		switch (pScrn->bitsPerPixel) {
		case 8:
			speed = p[1];
			break;
		case 15:
		case 16:
			speed = p[2];
			break;
		case 24:
			speed = p[3];
			break;
		case 32:
			speed = p[4];
			break;
		default:
			/* Should not get here */
			speed = 0;
			break;
		}
		pCir->MaxClock = speed;
		from = X_PROBED;
	}
	xf86DrvMsg(pScrn->scrnIndex, from, "Max pixel clock is %d MHz\n",
				pCir->MaxClock / 1000);

	/*
	 * Setup the ClockRanges, which describe what clock ranges are available,
	 * and what sort of modes they can be used for.
	 */
	clockRanges = xnfcalloc(sizeof(ClockRange), 1);
	clockRanges->next = NULL;
	clockRanges->minClock = pCir->MinClock;
	clockRanges->maxClock = pCir->MaxClock;
	clockRanges->clockIndex = -1;		/* programmable */
	clockRanges->interlaceAllowed = FALSE;	/* XXX check this */
	clockRanges->doubleScanAllowed = FALSE;	/* XXX check this */
	clockRanges->doubleScanAllowed = FALSE;	/* XXX check this */
	clockRanges->doubleScanAllowed = FALSE;	/* XXX check this */
	clockRanges->ClockMulFactor = 1;
	clockRanges->ClockDivFactor = 1;
	clockRanges->PrivFlags = 0;

	/* Depending upon what sized tiles used, either 128 or 256. */
	/* Aw, heck.  Just say 128. */
	pCir->Rounding = 128 >> pCir->BppShift;

	/*
	 * xf86ValidateModes will check that the mode HTotal and VTotal values
	 * don't exceed the chipset's limit if pScrn->maxHValue and
	 * pScrn->maxVValue are set.  Since our CIRValidMode() already takes
	 * care of this, we don't worry about setting them here.
	 */

	i = xf86ValidateModes(pScrn, pScrn->monitor->Modes, pScrn->display->modes,
							clockRanges,
							LgLinePitches[pScrn->bitsPerPixel / 8 - 1],
							0, 0, 128 * 8,
							0, 0, /* Any virtual height is allowed. */
							pScrn->display->virtualX,
							pScrn->display->virtualY,
							pCir->FbMapSize,
							LOOKUP_BEST_REFRESH);

	pCir->chip.lg->lineDataIndex = LgFindLineData(pScrn->displayWidth,
										pScrn->bitsPerPixel);

	if (i == -1) {
		LgFreeRec(pScrn);
		return FALSE;
	}

	/* Prune the modes marked as invalid */
	xf86PruneDriverModes(pScrn);

	if (i == 0 || pScrn->modes == NULL) {
		xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "No valid modes found\n");
		LgFreeRec(pScrn);
		return FALSE;
	}

	/*
	 * Set the CRTC parameters for all of the modes based on the type
	 * of mode, and the chipset's interlace requirements.
	 *
	 * Calling this is required if the mode->Crtc* values are used by the
	 * driver and if the driver doesn't provide code to set them.  They
	 * are not pre-initialised at all.
	 */
	xf86SetCrtcForModes(pScrn, INTERLACE_HALVE_V);

	/* Set the current mode to the first in the list */
	pScrn->currentMode = pScrn->modes;

	/* Print the list of modes being used */
	xf86PrintModes(pScrn);

	/* Set display resolution */
	xf86SetDpi(pScrn, 0, 0);

	/* Load bpp-specific modules */
	switch (pScrn->bitsPerPixel) {
	case 8:
	case 16:
	case 24:
	case 32: 
	    if (xf86LoadSubModule(pScrn, "fb") == NULL) {
	         LgFreeRec(pScrn);
		 return FALSE;
	    }
	    break;
	}

	/* Load XAA if needed */
	if (!pCir->NoAccel) {
#ifdef HAVE_XAA_H
		if (!xf86LoadSubModule(pScrn, "xaa"))
#else
		if (1)
#endif
                {
			xf86DrvMsg(pScrn->scrnIndex, X_INFO,
				   "Falling back to shadowfb\n");
			pCir->NoAccel = TRUE;
			pCir->shadowFB = TRUE;
		}
	}

	/* Load ramdac if needed */
	if (pCir->HWCursor) {
		if (!xf86LoadSubModule(pScrn, "ramdac")) {
			LgFreeRec(pScrn);
			return FALSE;
		}
	}

	if (pCir->shadowFB) {
	    if (!xf86LoadSubModule(pScrn, "shadowfb")) {
		LgFreeRec(pScrn);
		return FALSE;
	    }
	}
	
	return TRUE;
}

/*
 * This function saves the video state.
 */
static void
LgSave(ScrnInfoPtr pScrn)
{
	CirPtr pCir = CIRPTR(pScrn);
	vgaHWPtr hwp = VGAHWPTR(pScrn);

#ifdef LG_DEBUG
	ErrorF("LgSave\n");
#endif

	vgaHWSave(pScrn, &VGAHWPTR(pScrn)->SavedReg, VGA_SR_ALL);

	pCir->chip.lg->ModeReg.ExtVga[CR1A] = pCir->chip.lg->SavedReg.ExtVga[CR1A] = hwp->readCrtc(hwp, 0x1A);
	pCir->chip.lg->ModeReg.ExtVga[CR1B] = pCir->chip.lg->SavedReg.ExtVga[CR1B] = hwp->readCrtc(hwp, 0x1B);
	pCir->chip.lg->ModeReg.ExtVga[CR1D] = pCir->chip.lg->SavedReg.ExtVga[CR1D] = hwp->readCrtc(hwp, 0x1D);
	pCir->chip.lg->ModeReg.ExtVga[CR1E] = pCir->chip.lg->SavedReg.ExtVga[CR1E] = hwp->readCrtc(hwp, 0x1E);
	pCir->chip.lg->ModeReg.ExtVga[SR07] = pCir->chip.lg->SavedReg.ExtVga[SR07] = hwp->readSeq(hwp, 0x07);
	pCir->chip.lg->ModeReg.ExtVga[SR0E] = pCir->chip.lg->SavedReg.ExtVga[SR0E] = hwp->readSeq(hwp, 0x0E);
	pCir->chip.lg->ModeReg.ExtVga[SR12] = pCir->chip.lg->SavedReg.ExtVga[SR12] = hwp->readSeq(hwp, 0x12);
	pCir->chip.lg->ModeReg.ExtVga[SR13] = pCir->chip.lg->SavedReg.ExtVga[SR13] = hwp->readSeq(hwp, 0x13);
	pCir->chip.lg->ModeReg.ExtVga[SR1E] = pCir->chip.lg->SavedReg.ExtVga[SR1E] = hwp->readSeq(hwp, 0x1E);

	pCir->chip.lg->ModeReg.FORMAT = pCir->chip.lg->SavedReg.FORMAT = memrw(0xC0);
	
	pCir->chip.lg->ModeReg.VSC = pCir->chip.lg->SavedReg.VSC = memrl(0x3FC);
	
	pCir->chip.lg->ModeReg.DTTC = pCir->chip.lg->SavedReg.DTTC = memrw(0xEA);
	
	if (pCir->Chipset == PCI_CHIP_GD5465) {
	    pCir->chip.lg->ModeReg.TileCtrl = pCir->chip.lg->SavedReg.TileCtrl = memrw(0x2C4);
	}
	
	pCir->chip.lg->ModeReg.TILE = pCir->chip.lg->SavedReg.TILE = memrb(0x407);
	
	if (pCir->Chipset == PCI_CHIP_GD5465)
	    pCir->chip.lg->ModeReg.BCLK = pCir->chip.lg->SavedReg.BCLK = memrb(0x2C0);
	else
	    pCir->chip.lg->ModeReg.BCLK = pCir->chip.lg->SavedReg.BCLK = memrb(0x8C);
	
	pCir->chip.lg->ModeReg.CONTROL = pCir->chip.lg->SavedReg.CONTROL = memrw(0x402);
}

/*
 * Initialise a new mode.  This is currently still using the old
 * "initialise struct, restore/write struct to HW" model.  That could
 * be changed.
 */

static Bool
LgModeInit(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
	vgaHWPtr hwp;
	CirPtr pCir;
	int width;
	Bool VDiv2 = FALSE;
	CARD16 clockData;
	LgLineDataPtr lineData;

#ifdef LG_DEBUG
	ErrorF("LgModeInit %d bpp,   %d   %d %d %d %d   %d %d %d %d\n",
				pScrn->bitsPerPixel,
				mode->Clock,
				mode->HDisplay,
				mode->HSyncStart,
				mode->HSyncEnd,
				mode->HTotal,
				mode->VDisplay,
				mode->VSyncStart,
				mode->VSyncEnd,
				mode->VTotal);

	ErrorF("LgModeInit: depth %d bits\n", pScrn->depth);
#endif

	pCir = CIRPTR(pScrn);
	hwp = VGAHWPTR(pScrn);
	vgaHWUnlock(hwp);

	if (mode->VTotal >= 1024 && !(mode->Flags & V_INTERLACE)) {
		/* For non-interlaced vertical timing >= 1024, the vertical timings */
		/* are divided by 2 and VGA CRTC 0x17 bit 2 is set. */
		if (!mode->CrtcVAdjusted) {
			mode->CrtcVDisplay >>= 1;
			mode->CrtcVSyncStart >>= 1;
			mode->CrtcVSyncEnd >>= 1;
			mode->CrtcVTotal >>= 1;
			mode->CrtcVAdjusted = TRUE;
		}
		VDiv2 = TRUE;
	}

	/* Initialise the ModeReg values */
	if (!vgaHWInit(pScrn, mode))
		return FALSE;
	pScrn->vtSema = TRUE;

	if (VDiv2)
		hwp->ModeReg.CRTC[0x17] |= 0x04;

#ifdef LG_DEBUG
	ErrorF("SynthClock = %d\n", mode->SynthClock);
#endif
	hwp->IOBase = 0x3D0;
	hwp->ModeReg.MiscOutReg |= 0x01;
#if 0 /* Mono address */
	hwp->IOBase = 0x3B0;
	hwp->ModeReg.MiscOutReg &= ~0x01;
#endif


	/* ??? Should these be both ...End or ...Start, not one of each? */
	pCir->chip.lg->ModeReg.ExtVga[CR1A] = (((mode->CrtcVSyncStart + 1) & 0x300 ) >> 2)
								| (((mode->CrtcHSyncEnd >> 3) & 0xC0) >> 2);

	width = pScrn->displayWidth * pScrn->bitsPerPixel / 8;
	if (pScrn->bitsPerPixel == 1)
		width <<= 2;
	hwp->ModeReg.CRTC[0x13] = (width + 7) >> 3;
	/* Offset extension (see CR13) */
	pCir->chip.lg->ModeReg.ExtVga[CR1B] &= 0xEF;
	pCir->chip.lg->ModeReg.ExtVga[CR1B] |= (((width + 7) >> 3) & 0x100)?0x10:0x00;
	pCir->chip.lg->ModeReg.ExtVga[CR1B] |= 0x22;
	pCir->chip.lg->ModeReg.ExtVga[CR1D] = (((width + 7) >> 3) & 0x200)?0x01:0x00;

	/* Set the 28th bit to enable extended modes. */
	pCir->chip.lg->ModeReg.VSC = 0x10000000;

	/* Overflow register (sure are a lot of overflow bits around...) */
	pCir->chip.lg->ModeReg.ExtVga[CR1E] = 0x00;
	pCir->chip.lg->ModeReg.ExtVga[CR1E] |= ((mode->CrtcHTotal>>3 & 0x0100)?1:0)<<7;
	pCir->chip.lg->ModeReg.ExtVga[CR1E] |= ((mode->CrtcHDisplay>>3 & 0x0100)?1:0)<<6;
	pCir->chip.lg->ModeReg.ExtVga[CR1E] |= ((mode->CrtcHSyncStart>>3 & 0x0100)?1:0)<<5;
	pCir->chip.lg->ModeReg.ExtVga[CR1E] |= ((mode->CrtcHSyncStart>>3 & 0x0100)?1:0)<<4;
	pCir->chip.lg->ModeReg.ExtVga[CR1E] |= ((mode->CrtcVTotal & 0x0400)?1:0)<<3;
	pCir->chip.lg->ModeReg.ExtVga[CR1E] |= ((mode->CrtcVDisplay & 0x0400)?1:0)<<2;
	pCir->chip.lg->ModeReg.ExtVga[CR1E] |= ((mode->CrtcVSyncStart & 0x0400)?1:0)<<1;
	pCir->chip.lg->ModeReg.ExtVga[CR1E] |= ((mode->CrtcVSyncStart & 0x0400)?1:0)<<0;

	lineData = &LgLineData[pCir->chip.lg->lineDataIndex];

	pCir->chip.lg->ModeReg.TILE = lineData->tilesPerLine & 0x3F;

	if (8 == pScrn->bitsPerPixel) {
		pCir->chip.lg->ModeReg.FORMAT = 0x0000;

		pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.TILE << 8) | 0x0080
							| (lineData->width << 6);
		pCir->chip.lg->ModeReg.CONTROL = 0x0000 | (lineData->width << 11);


		/* There is an optimal FIFO threshold value (lower 5 bits of DTTC)
		   for every resolution and color depth combination.  We'll hit
		   the highlights here, and get close for anything that's not
		   covered. */
		if (mode->CrtcHDisplay <= 640) {
			/* BAD numbers:  0x1E */
			/* GOOD numbers:  0x14 */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x0014);
		} else if (mode->CrtcHDisplay <= 800) {
			/* BAD numbers:  0x16 */
			/* GOOD numbers:  0x13 0x14 */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x0014);
		} else if (mode->CrtcHDisplay <= 1024) {
			/* BAD numbers:  */
			/* GOOD numbers: 0x15 */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x0015);
		} else if (mode->CrtcHDisplay <= 1280) {
			/* BAD numbers:  */
			/* GOOD numbers:  0x16 */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x0016);
		} else {
			/* BAD numbers:  */
			/* GOOD numbers:  */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x0017);
		}
	} else if (16 == pScrn->bitsPerPixel) {
		/* !!! Assume 5-6-5 RGB mode (for now...) */
		pCir->chip.lg->ModeReg.FORMAT = 0x1400;

		if (pScrn->depth == 15)
			pCir->chip.lg->ModeReg.FORMAT = 0x1600;

		pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.TILE << 8) | 0x0080
							| (lineData->width << 6);
		pCir->chip.lg->ModeReg.CONTROL = 0x2000 | (lineData->width << 11);

		if (mode->CrtcHDisplay <= 640) {
			/* BAD numbers:  0x12 */
			/* GOOD numbers: 0x10 */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x0010);
		} else if (mode->CrtcHDisplay <= 800) {
			/* BAD numbers:  0x13 */
			/* GOOD numbers:  0x11 */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x0011);
		} else if (mode->CrtcHDisplay <= 1024) {
			/* BAD numbers:  0x14 */
			/* GOOD numbers: 0x12  */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x0012);
		} else if (mode->CrtcHDisplay <= 1280) {
			/* BAD numbers:   0x08 0x10 */
			/* Borderline numbers: 0x12 */
			/* GOOD numbers:  0x15 */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x0015);
		} else {
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x0017);
		}
	} else if (24 == pScrn->bitsPerPixel) {
		pCir->chip.lg->ModeReg.FORMAT = 0x2400;

		pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.TILE << 8) | 0x0080
							| (lineData->width << 6);
		pCir->chip.lg->ModeReg.CONTROL = 0x4000 | (lineData->width << 11);

		if (mode->CrtcHDisplay <= 640) {
			/* BAD numbers:   */
			/* GOOD numbers:  0x10 */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x0010);
		} else if (mode->CrtcHDisplay <= 800) {
			/* BAD numbers:   */
			/* GOOD numbers:   0x11 */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x0011);
		} else if (mode->CrtcHDisplay <= 1024) {
			/* BAD numbers:  0x12 0x13 */
			/* Borderline numbers:  0x15 */
			/* GOOD numbers:  0x17 */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x0017);
		} else if (mode->CrtcHDisplay <= 1280) {
			/* BAD numbers:   */
			/* GOOD numbers:  0x1E */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x001E);
		} else {
			/* BAD numbers:   */
			/* GOOD numbers:  */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x0020);
		}
	} else if (32 == pScrn->bitsPerPixel) {
		pCir->chip.lg->ModeReg.FORMAT = 0x3400;

		pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.TILE << 8) | 0x0080
							| (lineData->width << 6);
		pCir->chip.lg->ModeReg.CONTROL = 0x6000 | (lineData->width << 11);

		if (mode->CrtcHDisplay <= 640) {
			/* GOOD numbers:  0x0E */
			/* BAD numbers:  */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x000E);
		} else if (mode->CrtcHDisplay <= 800) {
			/* GOOD numbers:  0x17 */
			/* BAD numbers:  */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x0017);
		} else if (mode->CrtcHDisplay <= 1024) {
			/* GOOD numbers: 0x1D */
			/* OKAY numbers:  0x15 0x14 0x16 0x18 0x19 */
			/* BAD numbers:  0x0E 0x12 0x13 0x0D */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x001D);
		} else if (mode->CrtcHDisplay <= 1280) {
			/* GOOD numbers:  */
			/* BAD numbers:  */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x0022); /* 10 */
		} else {
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xFFE0) | (0x0024);
		}
	} else {
		/* ??? What could it be?  Use some sane numbers. */
	}

	/* Setup the appropriate memory interleaving */
	pCir->chip.lg->ModeReg.DTTC |= (pCir->chip.lg->memInterleave << 8);
	pCir->chip.lg->ModeReg.TILE |= pCir->chip.lg->memInterleave & 0xC0;

	if (PCI_CHIP_GD5465 == pCir->Chipset) {
		/* The tile control information in the DTTC is also mirrored
		   elsewhere. */
		pCir->chip.lg->ModeReg.TileCtrl = pCir->chip.lg->ModeReg.DTTC & 0xFFC0;

		/* The 5465's DTTC records _fetches_ per line, not
		   tiles per line.  Fetchs are 128-byte fetches. */
		if (pCir->chip.lg->ModeReg.DTTC & 0x0040) {
			/* Using 256-byte wide tiles.  Double the fetches
			   per line field. */
			pCir->chip.lg->ModeReg.DTTC = (pCir->chip.lg->ModeReg.DTTC & 0xC0FF)
								| ((pCir->chip.lg->ModeReg.DTTC & 0x3F00) << 1);
		}
	}

	/* Program the registers */
	vgaHWProtect(pScrn, TRUE);
	hwp->writeMiscOut(hwp, hwp->ModeReg.MiscOutReg);

	clockData = LgSetClock(pCir, hwp, mode->SynthClock);
	pCir->chip.lg->ModeReg.ExtVga[SR0E] = (clockData >> 8) & 0xFF;
	pCir->chip.lg->ModeReg.ExtVga[SR1E] = clockData & 0xFF;

	/* Write those registers out to the card. */
	LgRestoreLgRegs(pScrn, &pCir->chip.lg->ModeReg);

	/* Programme the registers */
	vgaHWRestore(pScrn, &hwp->ModeReg, VGA_SR_MODE | VGA_SR_CMAP);

	vgaHWProtect(pScrn, FALSE);

	return TRUE;
}

static int LgFindLineData(int displayWidth, int bpp)
{
	/* Find the smallest tile-line-pitch such that the total byte pitch
	 is greater than or equal to displayWidth*Bpp. */
	int i;

	/* Some pitch sizes are duplicates in the table.  BUT, the invariant is
	 that the _first_ time a pitch occurs in the table is always _before_
	 all other pitches greater than it.  Said in another way... if all
	 duplicate entries from the table were removed, then the resulting pitch
	 values are strictly increasing. */

	for (i = 0; LgLineData[i].pitch > 0; i++)
		if (LgLineData[i].pitch >= displayWidth*bpp>>3)
			return i;

	/* Um, uh oh! */
	return -1;
}




static void
LgRestoreLgRegs(ScrnInfoPtr pScrn, LgRegPtr lgReg)
{
	CirPtr pCir;
	vgaHWPtr hwp;
	CARD8 cr1D;

	pCir = CIRPTR(pScrn);

	/* First, VGAish registers. */
	hwp = VGAHWPTR(pScrn);
	hwp->writeCrtc(hwp, 0x1A, lgReg->ExtVga[CR1A]);
	hwp->writeCrtc(hwp, 0x1B, lgReg->ExtVga[CR1B]);
	cr1D = (hwp->readCrtc(hwp, 0x1D) & ~1) | (lgReg->ExtVga[CR1D] & 0x01);
	hwp->writeCrtc(hwp, 0x1D, cr1D);
	hwp->writeCrtc(hwp, 0x1E, lgReg->ExtVga[CR1E]);

	hwp->writeSeq(hwp, 0x07, lgReg->ExtVga[SR07]);
	hwp->writeSeq(hwp, 0x0E, lgReg->ExtVga[SR0E]);
	hwp->writeSeq(hwp, 0x12, lgReg->ExtVga[SR12]);
	hwp->writeSeq(hwp, 0x13, lgReg->ExtVga[SR13]);
	hwp->writeSeq(hwp, 0x1E, lgReg->ExtVga[SR1E]);
	memww(0xC0, lgReg->FORMAT);
  
    /* Vendor Specific Control is touchy.  Only bit 28 is of concern. */
	memwl(0x3FC, ((memrl(0x3FC) & ~(1<<28)) | (lgReg->VSC & (1<<28))));
	
	memww(0xEA, lgReg->DTTC);
  
	if (pCir->Chipset == PCI_CHIP_GD5465) {
	    memww(0x2C4, lgReg->TileCtrl);
	}
  
	memwb(0x407, lgReg->TILE);
        
	if (pCir->Chipset == PCI_CHIP_GD5465)
	    memwb(0x2C0, lgReg->BCLK);
	else
	    memwb(0x8C, lgReg->BCLK);
    
	memww(0x402, lgReg->CONTROL);
}

/*
 * Restore the initial (text) mode.
 */
static void
LgRestore(ScrnInfoPtr pScrn)
{
	vgaHWPtr hwp;
	vgaRegPtr vgaReg;
	CirPtr pCir;
	LgRegPtr lgReg;

#ifdef LG_DEBUG
	ErrorF("LgRestore  pScrn = %p\n", (void *)pScrn);
#endif

	pCir = CIRPTR(pScrn);
	hwp = VGAHWPTR(pScrn);
	vgaReg = &hwp->SavedReg;
	lgReg = &pCir->chip.lg->SavedReg;

	vgaHWProtect(pScrn, TRUE);

	LgRestoreLgRegs(pScrn, lgReg);

	vgaHWRestore(pScrn, vgaReg, VGA_SR_ALL);
	vgaHWProtect(pScrn, FALSE);
}

/* Mandatory */

/* This gets called at the start of each server generation */

Bool
LgScreenInit(SCREEN_INIT_ARGS_DECL)
{
	/* The vgaHW references will disappear one day */
	ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
	vgaHWPtr hwp;
	CirPtr pCir;
	int i, ret;
	VisualPtr visual;
	int displayWidth,width,height;
	unsigned char * FbBase = NULL;

#ifdef LG_DEBUG
	ErrorF("LgScreenInit\n");
#endif

	hwp = VGAHWPTR(pScrn);

	hwp->MapSize = 0x10000;		/* Standard 64k VGA window */

	pCir = CIRPTR(pScrn);

	/* Map the VGA memory and get the VGA IO base */
	if (!vgaHWMapMem(pScrn))
		return FALSE;

	/* Map the CIR memory and MMIO areas */
	if (!CirMapMem(pCir, pScrn->scrnIndex))
		return FALSE;
#ifdef EXPERIMENTAL
	lg_vgaHWSetMmioFunc(hwp, pCir->IOBase);
#endif
	vgaHWGetIOBase(hwp);

	/* Save the current state */
	LgSave(pScrn);

	/* Initialise the first mode */
	if (!LgModeInit(pScrn, pScrn->currentMode))
		return FALSE;

	/* Make things beautiful */
	LgSaveScreen(pScreen, SCREEN_SAVER_ON);

	/* Set the viewport */
	LgAdjustFrame(ADJUST_FRAME_ARGS(pScrn, pScrn->frameX0, pScrn->frameY0));

	/*
	 * The next step is to setup the screen's visuals, and initialise the
	 * framebuffer code.  In cases where the framebuffer's default
	 * choices for things like visual layouts and bits per RGB are OK,
	 * this may be as simple as calling the framebuffer's ScreenInit()
	 * function.  If not, the visuals will need to be setup before calling
	 * a fb ScreenInit() function and fixed up after.
	 *
	 */

	/*
	 * Reset the visual list.
	 */
	miClearVisualTypes();

	/* Setup the visuals we support. */

	if (!miSetVisualTypes(pScrn->depth,
			      miGetDefaultVisualMask(pScrn->depth),
			      pScrn->rgbBits, pScrn->defaultVisual))
	  return FALSE;

	miSetPixmapDepths ();

#ifdef LG_DEBUG
	ErrorF("LgScreenInit after miSetVisualTypes\n");
#endif
	displayWidth = pScrn->displayWidth;
	if (pCir->rotate) {
	    height = pScrn->virtualX;
	    width = pScrn->virtualY;
	} else {
	    width = pScrn->virtualX;
	    height = pScrn->virtualY;
	}
	
	if(pCir->shadowFB) {
	    pCir->ShadowPitch = BitmapBytePad(pScrn->bitsPerPixel * width);
	    pCir->ShadowPtr = malloc(pCir->ShadowPitch * height);
	    displayWidth = pCir->ShadowPitch / (pScrn->bitsPerPixel >> 3);
	    FbBase = pCir->ShadowPtr;
	} else {
	    pCir->ShadowPtr = NULL;
	    FbBase = pCir->FbBase;
	}

	/*
	 * Call the framebuffer layer's ScreenInit function, and fill in other
	 * pScreen fields.
	 */
	switch (pScrn->bitsPerPixel) {
	case 8:
	case 16:
	case 24:
	case 32:
	    ret = fbScreenInit(pScreen, FbBase,
				width,height,
				pScrn->xDpi, pScrn->yDpi,
				displayWidth,pScrn->bitsPerPixel);
	    break;
	default:
		xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
			   "X11: Internal error: invalid bpp (%d) in LgScreenInit\n",
			   pScrn->bitsPerPixel);
		ret = FALSE;
		break;
	}
	if (!ret)
		return FALSE;

#ifdef LG_DEBUG
	ErrorF("LgScreenInit after depth dependent init\n");
#endif

	/* Override the default mask/offset settings */
	if (pScrn->bitsPerPixel > 8) {
		for (i = 0; i < pScreen->numVisuals; i++) {
			visual = &pScreen->visuals[i];
			if ((visual->class | DynamicClass) == DirectColor) {
				visual->offsetRed = pScrn->offset.red;
				visual->offsetGreen = pScrn->offset.green;
				visual->offsetBlue = pScrn->offset.blue;
				visual->redMask = pScrn->mask.red;
				visual->greenMask = pScrn->mask.green;
				visual->blueMask = pScrn->mask.blue;
			}
		}
	}

	/* must be after RGB ordering fixed */

	fbPictureInit(pScreen, 0, 0);

	/*
	 * Set initial black & white colourmap indices.
	 */
	xf86SetBlackWhitePixels(pScreen);

#ifdef HAVE_XAA_H
	if (!pCir->NoAccel) { /* Initialize XAA functions */
		if (!LgXAAInit(pScreen))
			xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Could not initialize XAA\n");
	}
#endif
#if 1
	pCir->DGAModeInit = LgModeInit;
	if (!CirDGAInit(pScreen))
	  xf86DrvMsg(pScrn->scrnIndex, X_ERROR, 
		     "DGA initialization failed\n");
#endif
        xf86SetSilkenMouse(pScreen);

	/* Initialise cursor functions */
	miDCInitialize(pScreen, xf86GetPointerScreenFuncs());

	if (pCir->HWCursor) { /* Initialize HW cursor layer */
		if (!LgHWCursorInit(pScreen))
			xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
				"Hardware cursor initialization failed\n");
	}

	/* Initialise default colourmap */
	if (!miCreateDefColormap(pScreen))
		return FALSE;

	if (pScrn->bitsPerPixel > 1 && pScrn->bitsPerPixel <= 8)
		vgaHWHandleColormaps(pScreen);

	xf86DPMSInit(pScreen, LgDisplayPowerManagementSet, 0);

	pScrn->memPhysBase = pCir->FbAddress;
	pScrn->fbOffset = 0;

	{
		XF86VideoAdaptorPtr *ptr;
		int n;

		n = xf86XVListGenericAdaptors(pScrn,&ptr);
		if (n)
			xf86XVScreenInit(pScreen, ptr, n);
	}

	/*
	 * Wrap the CloseScreen vector and set SaveScreen.
	 */
	pScreen->SaveScreen = LgSaveScreen;
	pCir->CloseScreen = pScreen->CloseScreen;
	pScreen->CloseScreen = LgCloseScreen;

	/* Report any unused options (only for the first generation) */
	if (serverGeneration == 1)
		xf86ShowUnusedOptions(pScrn->scrnIndex, pScrn->options);

	/* Done */
	return TRUE;
}


/* Usually mandatory */
Bool
LgSwitchMode(SWITCH_MODE_ARGS_DECL)
{
	SCRN_INFO_PTR(arg);
	return LgModeInit(pScrn, mode);
}

#define ROUND_DOWN(x, mod)	(((x) / (mod)) * (mod))
#define ROUND_UP(x, mod)	((((x) + (mod) - 1) / (mod)) * (mod))

/*
 * This function is used to initialize the Start Address - the first
 * displayed location in the video memory.
 */
/* Usually mandatory */
void
LgAdjustFrame(ADJUST_FRAME_ARGS_DECL)
{
	SCRN_INFO_PTR(arg);
	int Base, tmp;
	CirPtr pCir = CIRPTR(pScrn);
	vgaHWPtr hwp = VGAHWPTR(pScrn);
	int cursorX, cursorY;
	int middleX, middleY;
	const LgLineDataPtr lineData = &LgLineData[pCir->chip.lg->lineDataIndex];
	const int viewportXRes =
		(PCI_CHIP_GD5465 == pCir->Chipset) ? (24==pScrn->bitsPerPixel?24:1) :
			(lineData->width?256:128) /
			(24==pScrn->bitsPerPixel?1:(pScrn->bitsPerPixel>>3));
	const int viewportYRes =
		(PCI_CHIP_GD5465 == pCir->Chipset) ? 1 : (24==pScrn->bitsPerPixel?3:1);

	/* Where's the pointer? */
	miPointerGetPosition(inputInfo.pointer, &cursorX, &cursorY);

	/* Where's the middle of the screen?  We want to eventually know
	   which side of the screen the pointer is on. */
	middleX = (pScrn->frameX1 + pScrn->frameX0) / 2;
	middleY = (pScrn->frameY1 + pScrn->frameY0) / 2;

	if (cursorX < middleX) {
		/* Pointer is on left side of screen.  Round the frame value down. */
		pScrn->frameX0 = ROUND_DOWN(pScrn->frameX0, viewportXRes);
	} else {
		/* Pointer is on right side of screen.  Round the frame value
		   up.  A side effect of this rounding up is that we might expose
		   a part of the screen that's actually on the far /left/ of the
		   frame buffer.  That's because, although the virtual desktop might
		   be an integral number of tiles, the display might not.  We'll
		   just live with this artifact. */
		pScrn->frameX0 = ROUND_UP(pScrn->frameX0, viewportXRes);
	}
	pScrn->frameX1 = pScrn->frameX0 + pScrn->currentMode->HDisplay - 1;

	if (cursorY < middleY) {
		pScrn->frameY0 = ROUND_DOWN(pScrn->frameY0, viewportYRes);
	} else {
		pScrn->frameY0 = ROUND_UP(pScrn->frameY0, viewportYRes);
	}
	pScrn->frameY1 = pScrn->frameY0 + pScrn->currentMode->VDisplay - 1;


	if (x != pScrn->frameX0 || y != pScrn->frameY0) {
		/* !!! */
		/* We moved the frame from where xf86SetViewport() placed it.
		   If we're using a SW cursor, that's okay -- the pointer exists in
		   the framebuffer, and those bits are still all aligned.  But
		   if we're using a HW cursor, then we need to re-align the pointer.
		   Call SetCursorPosition() with the appropriate new pointer
		   values, adjusted to be wrt the new frame. */

		x = pScrn->frameX0;
		y = pScrn->frameY0;
	}

	/* ??? Will this work for 1bpp?  */
	Base = (y * lineData->pitch + (x*pScrn->bitsPerPixel/8)) / 4;

	if ((Base & ~0x000FFFFF) != 0) {
		/* ??? */
		ErrorF("X11: Internal error: LgAdjustFrame: cannot handle overflow\n");
		return;
	}

	hwp->writeCrtc(hwp, 0x0C, (Base >> 8) & 0xFF);
	hwp->writeCrtc(hwp, 0x0D, Base & 0xFF);
	tmp = hwp->readCrtc(hwp, 0x1B) & 0xF2;
	tmp |= (Base >> 16) & 0x01;
	tmp |= (Base >> 15) & 0x0C;
	hwp->writeCrtc(hwp, 0x1B, tmp);
	tmp = hwp->readCrtc(hwp, 0x1D) & 0xE7;
	tmp |= (Base >> 16) & 0x18;
	hwp->writeCrtc(hwp, 0x1D, tmp);
}

/*
 * This is called when VT switching back to the X server.  Its job is
 * to reinitialise the video mode.
 *
 * We may wish to unmap video/MMIO memory too.
 */

/* Mandatory */
Bool
LgEnterVT(VT_FUNC_ARGS_DECL)
{
	SCRN_INFO_PTR(arg);
	CirPtr pCir = CIRPTR(pScrn);
#ifdef LG_DEBUG
	ErrorF("LgEnterVT\n");
#endif

	/* XXX Shouldn't this be in LeaveVT? */
	/* Disable HW cursor */
	if (pCir->HWCursor)
		LgHideCursor(pScrn);

	/* Should we re-save the text mode on each VT enter? */
	return LgModeInit(pScrn, pScrn->currentMode);
}


/*
 * This is called when VT switching away from the X server.  Its job is
 * to restore the previous (text) mode.
 *
 * We may wish to remap video/MMIO memory too.
 */

/* Mandatory */
void
LgLeaveVT(VT_FUNC_ARGS_DECL)
{
	SCRN_INFO_PTR(arg);
	vgaHWPtr hwp = VGAHWPTR(pScrn);
	CirPtr pCir = CIRPTR(pScrn);
#ifdef LG_DEBUG
	ErrorF("LgLeaveVT\n");
#endif

	/* XXX Shouldn't this be in EnterVT? */
	/* Enable HW cursor */
	if (pCir->HWCursor)
		LgShowCursor(pScrn);

	LgRestore(pScrn);
	vgaHWLock(hwp);
}


/*
 * This is called at the end of each server generation.  It restores the
 * original (text) mode.  It should also unmap the video memory, and free
 * any per-generation data allocated by the driver.  It should finish
 * by unwrapping and calling the saved CloseScreen function.
 */

/* Mandatory */
static Bool
LgCloseScreen(CLOSE_SCREEN_ARGS_DECL)
{
	ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
	vgaHWPtr hwp = VGAHWPTR(pScrn);
	CirPtr pCir = CIRPTR(pScrn);

	if(pScrn->vtSema) {
	LgRestore(pScrn);
	if (pCir->HWCursor)
	    LgHideCursor(pScrn);

	vgaHWLock(hwp);
	
	CirUnmapMem(pCir, pScrn->scrnIndex);
	}

#ifdef HAVE_XAA_H
	if (pCir->AccelInfoRec)
		XAADestroyInfoRec(pCir->AccelInfoRec);
	pCir->AccelInfoRec = NULL;
#endif

	if (pCir->CursorInfoRec)
		xf86DestroyCursorInfoRec(pCir->CursorInfoRec);
	pCir->CursorInfoRec = NULL;
	if (pCir->DGAModes)
		free(pCir->DGAModes);
	pCir->DGAnumModes = 0;
	pCir->DGAModes = NULL;

	pScrn->vtSema = FALSE;

	pScreen->CloseScreen = pCir->CloseScreen;
	return (*pScreen->CloseScreen)(CLOSE_SCREEN_ARGS);
}


/* Free up any persistent data structures */

/* Optional */
void
LgFreeScreen(FREE_SCREEN_ARGS_DECL)
{
	SCRN_INFO_PTR(arg);
#ifdef LG_DEBUG
	ErrorF("LgFreeScreen\n");
#endif
	/*
	 * This only gets called when a screen is being deleted.  It does not
	 * get called routinely at the end of a server generation.
	 */
	if (xf86LoaderCheckSymbol("vgaHWFreeHWRec"))
		vgaHWFreeHWRec(pScrn);
	LgFreeRec(pScrn);
}


/* Checks if a mode is suitable for the selected chipset. */

/* Optional */
ModeStatus
LgValidMode(SCRN_ARG_TYPE arg, DisplayModePtr mode, Bool verbose, int flags)
{
	int lace;

	lace = 1 + ((mode->Flags & V_INTERLACE) != 0);

	if ((mode->CrtcHDisplay <= 2048) &&
		(mode->CrtcHSyncStart <= 4096) &&
		(mode->CrtcHSyncEnd <= 4096) &&
		(mode->CrtcHTotal <= 4096) &&
		(mode->CrtcVDisplay <= 2048 * lace) &&
		(mode->CrtcVSyncStart <= 4096 * lace) &&
		(mode->CrtcVSyncEnd <= 4096 * lace) &&
		(mode->CrtcVTotal <= 4096 * lace)) {
		return(MODE_OK);
	}
	return(MODE_BAD);
}


/* Do screen blanking */

/* Mandatory */
static Bool
LgSaveScreen(ScreenPtr pScreen, int mode)
{
	CirPtr pCir = CIRPTR(xf86ScreenToScrn(pScreen));
	ScrnInfoPtr pScrn = NULL;
	Bool unblank;

	unblank = xf86IsUnblank(mode);

	if (pScreen != NULL)
	    pScrn = xf86ScreenToScrn(pScreen);

	if (pScrn != NULL && pScrn->vtSema) {
	    if (unblank)
		/* Power up the palette DAC */
		memwb(0xB0,memrb(0xB0) & 0x7F);
	    else
		/* Power down the palette DAC */
		memwb(0xB0,memrb(0xB0) | 0x80);
	}
	
	return vgaHWSaveScreen(pScreen, mode);
}

static CARD16
LgSetClock(CirPtr pCir, vgaHWPtr hwp, int freq)
{
	int ffreq, num, den;
	CARD8 tmp;

	ErrorF("LgSetClock freq=%d.%03dMHz\n", freq / 1000, freq % 1000);

	ffreq = freq;
	if (!CirrusFindClock(&ffreq, pCir->MaxClock, &num, &den))
		return 0;

	ErrorF("LgSetClock: nom=%x den=%x ffreq=%d.%03dMHz\n",
		num, den, ffreq / 1000, ffreq % 1000);

	/* Set VCLK3. */
	/* The numerator and denominator registers are switched
	   around in the Laguna chips. */
	tmp = hwp->readSeq(hwp, 0x0E);
	hwp->writeSeq(hwp, 0x0E, (tmp & 0x80) | den);
	hwp->writeSeq(hwp, 0x1E, num);

	return (den << 8) | num;
}

/*
 * CIRDisplayPowerManagementSet --
 *
 * Sets VESA Display Power Management Signaling (DPMS) Mode.
 */
static void
LgDisplayPowerManagementSet(ScrnInfoPtr pScrn, int PowerManagementMode,
							int flags)
{
	unsigned char sr01, cr1a;
	vgaHWPtr hwp;

#ifdef LG_DEBUG
	ErrorF("LgDisplayPowerManagementSet: %d\n", PowerManagementMode);
#endif

	hwp = VGAHWPTR(pScrn);

	switch (PowerManagementMode) {
	case DPMSModeOn:
		/* Screen: On; HSync: On, VSync: On */
		sr01 = 0x00;
		cr1a = 0x00;
		break;
	case DPMSModeStandby:
		/* Screen: Off; HSync: Off, VSync: On */
		sr01 = 0x20;
		cr1a = 0x08;
		break;
	case DPMSModeSuspend:
		/* Screen: Off; HSync: On, VSync: Off */
		sr01 = 0x20;
		cr1a = 0x04;
		break;
	case DPMSModeOff:
		/* Screen: Off; HSync: Off, VSync: Off */
		sr01 = 0x20;
		cr1a = 0x0c;
		break;
	default:
		return;
	}

	sr01 |= hwp->readSeq(hwp, 0x01) & ~0x20;
	hwp->writeSeq(hwp, 0x01, sr01);
	cr1a |= hwp->readCrtc(hwp, 0x1A) & ~0x0C;
	hwp->writeCrtc(hwp, 0x1A, cr1a);
}

#define minb(p) MMIO_IN8(hwp->MMIOBase, (p))
#define moutb(p,v) MMIO_OUT8(hwp->MMIOBase, (p),(v))

static void
mmioWriteCrtc(vgaHWPtr hwp, CARD8 index, CARD8 value)
{
  moutb(index << 2, value);
}

static CARD8
mmioReadCrtc(vgaHWPtr hwp, CARD8 index)
{
  return minb(index << 2);
}

static void
lg_vgaHWSetMmioFunc(vgaHWPtr hwp, CARD8 *base)
{
    hwp->writeCrtc		= mmioWriteCrtc;
    hwp->readCrtc		= mmioReadCrtc;
    hwp->MMIOBase		= base;
    hwp->MMIOOffset		= 0;
}
